﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SurfaceCardsBoardLibrary.Interfaces;
using System.Xml;
using System.Reflection;
using SurfaceCardsBoardLibrary;
using System.Collections;
using System.Windows;
using SurfaceCardsGameKit.Games.BasicGame;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using System.Windows.Media;

namespace SurfaceCardsGameKit
{
    #region enum
    /// <summary>
    /// Enum to follow different states of the game
    /// </summary>
    public enum GameState
    {
        None,
        OnePlay,
        TwoPlay,
        OnePlayBattle,
        TwoPlayBattle,
        Battle,
        FinalyzeGame,
        Reset,
    };
    #endregion
    public class Battle : BasicGame, IEvaluate
    {
        #region Field
        /// <summary>
        /// To keep value of the cards for the game
        /// </summary>
        private XmlDocument xml = new XmlDocument();
        /// <summary>
        /// To access to the game board
        /// </summary>
        private Board board;

        private GameState gameState = GameState.None;
        private bool battle = false;
        /// <summary>
        /// Two decks where players can put cards
        /// </summary>
        private Deck[] gameDecks = new Deck[2];
        /// <summary>
        /// two decks, represent the players
        /// </summary>
        private Deck[] decks = new Deck[2];
        /// <summary>
        /// know who is the winner for this match
        /// </summary>
        private Deck winner = null;
        /// <summary>
        /// list of all cards actually "on game"
        /// </summary>
        private List<ICard> cards = new List<ICard>();
        #endregion

        #region constructor
        /// <summary>
        /// Create the game, load cards values and init all cards and decks
        /// </summary>
        /// <param name="board"></param>
        public Battle(Board board)
            : base(board)
        {
            this.board = board;
            this.LoadValue();
            this.Init();
        }
        #endregion

        #region Init
        private void Init()
        {
            this.InitDecks();
            this.InitCards();
            this.InitGame();
            this.board.IsCollidePathEnabled = false;
        }
        /// <summary>
        /// create the two "Game decks"
        /// </summary>
        private void InitDecks()
        {
            this.DeckScatterViewItemStyle = Application.Current.FindResource("DeckScatterViewItemStyle") as Style;

            this.gameDecks[0] = this.CreateDeck();
            this.gameDecks[0].IsMosaicEnabled = false;
            this.gameDecks[0].IsEyeEnabled = false;
            this.gameDecks[0].CanMove = false;
            this.gameDecks[0].CanScale = false;
            this.gameDecks[0].CanRotate = false;
            this.gameDecks[0].CanRemoveByUser = false;
            this.gameDecks[0].IsMenuEnabled = false;
            this.gameDecks[0].AutoRemoveWhenEmpty = false;
            this.gameDecks[0].CardAddingFace = CardInDeckFace.FrontUp;
            this.gameDecks[0].Center = new Point(513, 293);
            this.gameDecks[0].DeckDisposition = DeckDisposition.Heap;
            this.gameDecks[0].CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(FinalizeBattle);

            this.gameDecks[1] = this.CreateDeck();
            this.gameDecks[1].IsMosaicEnabled = false;
            this.gameDecks[1].IsEyeEnabled = false;
            this.gameDecks[1].IsMenuEnabled = false;
            this.gameDecks[1].CanMove = false;
            this.gameDecks[1].CanRotate = false;
            this.gameDecks[1].CanRemoveByUser = false;
            this.gameDecks[1].AutoRemoveWhenEmpty = false;
            this.gameDecks[1].CanScale = false;
            this.gameDecks[1].CardAddingFace = CardInDeckFace.FrontUp;
            this.gameDecks[1].Center = new Point(513, 485);
            this.gameDecks[1].DeckDisposition = DeckDisposition.Heap;
            this.gameDecks[1].CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(FinalizeBattle);

            this.gameDecks[0].Visibility = Visibility.Hidden;
            this.gameDecks[1].Visibility = Visibility.Hidden;
        }

        /// <summary>
        /// Create and init all cards, share the half for both players
        /// </summary>
        private void InitCards()
        {
            this.CardScatterViewItemStyle = Application.Current.FindResource("CardScatterViewItemStyle") as Style;

            this.CreateBrushesFor52Cards();
            this.Add52CardsToBoard();
            this.ShuffleCardsPositions(this.board.Cards.OfType<Card>());

            IEnumerator rator = this.board.Cards.OfType<Card>().GetEnumerator();

            rator.MoveNext();
            this.decks[0] = this.CreateDeck();
            this.decks[0].Center = new Point(512, 100);
            this.decks[0].CardAddingPosition = CardInDeckPosition.Bottom;
            this.decks[0].AutoRemoveWhenEmpty = false;
            this.decks[0].CardAddingFace = CardInDeckFace.BackUp;
            this.decks[0].CardRemovingFace = CardInDeckFace.BackUp;
            this.decks[0].CanScale = false;
            this.decks[0].IsMenuEnabled = false;
            this.decks[0].DeckDisposition = DeckDisposition.Stack;
            this.decks[0].IsEyeEnabled = false;
            this.decks[0].IsMosaicEnabled = false;
            Card tmp;
            using (this.decks[0].DeferUpdateDisposition(true, true))
            {
                for (int i = 0; i < 26; i++)
                {
                    tmp = rator.Current as Card;
                    tmp.CanTurnByUser = false;
                    tmp.IsBookEnabled = false;
                    tmp.ShadowColor = Colors.Red;
                    tmp.ShadowMargin = new Thickness(-10);
                    tmp.IsFlipSoundEnabled = false;
                    this.board.AddCardToDeck(this.decks[0] as IDeck, tmp);
                    rator.MoveNext();
                }
            }
            DateTime seed = DateTime.Now;
            this.decks[0].Shuffle(seed.Millisecond);

            this.decks[1] = this.CreateDeck();
            this.decks[1].Center = new Point(512, 680);
            this.decks[1].CardAddingPosition = CardInDeckPosition.Bottom;
            this.decks[1].CardAddingFace = CardInDeckFace.BackUp;
            this.decks[1].CardRemovingFace = CardInDeckFace.BackUp;
            this.decks[1].DeckDisposition = DeckDisposition.Stack;
            this.decks[1].AutoRemoveWhenEmpty = false;
            this.decks[1].CanScale = false;
            this.decks[1].IsMenuEnabled = false;
            this.decks[1].IsEyeEnabled = false;
            this.decks[1].IsMosaicEnabled = false;
            using (this.decks[1].DeferUpdateDisposition(true, true))
            {
                for (int i = 26; i < 52; i++)
                {
                    tmp = rator.Current as Card;
                    tmp.CanTurnByUser = false;
                    tmp.IsBookEnabled = false;
                    tmp.ShadowMargin = new Thickness(-10);
                    tmp.ShadowColor = Colors.Red;
                    tmp.IsFlipSoundEnabled = false;
                    this.board.AddCardToDeck(this.decks[1] as IDeck, tmp);
                    rator.MoveNext();
                }
            }
            this.decks[1].Shuffle(seed.Second);
        }

        /// <summary>
        /// Place Handler on the board to catch every movements during the game
        /// </summary>
        private void InitGame()
        {
            this.GameBackground = Application.Current.FindResource("TapisBataille") as DrawingBrush;
            this.board.AddHandler(Board.ManipulableObjectStateChangingEvent, new ManipulableObjectStateChangingEventHandler(ChangeState));
            this.board.AddHandler(ScatterViewItem.ScatterManipulationDeltaEvent, new ScatterManipulationDeltaEventHandler(OnScatterManipulationDelta));
        }
        #endregion

        #region Game

        #region Movements
        private void OnScatterManipulationDelta(Object sender, ScatterManipulationDeltaEventArgs e)
        {
            if (e.OriginalSource is ScatterViewItem)
            {
                ScatterViewItem svi = e.OriginalSource as ScatterViewItem;
                Card card = svi.ToCard() as Card;
                if (card != null)
                {
                    if (card.LastParent == this.decks[1] && (SurfaceHelper.TestScatterViewCollision(svi, this.gameDecks[0].ScatterViewItem) || SurfaceHelper.TestScatterViewCollision(svi, this.decks[0].ScatterViewItem)))
                        card.ShadowVisibility = Visibility.Visible;
                    else if (card.LastParent == this.decks[0] && (SurfaceHelper.TestScatterViewCollision(svi, this.gameDecks[1].ScatterViewItem) || SurfaceHelper.TestScatterViewCollision(svi, this.decks[1].ScatterViewItem)))
                        card.ShadowVisibility = Visibility.Visible;
                    else
                        card.ShadowVisibility = Visibility.Collapsed;
                }
            }
        }
        /// <summary>
        /// Catch every action on the board from cards and decks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ChangeState(Object sender, ManipulableObjectStateChangingEventArgs e)
        {
            if (e.ManipulableObjectAction == ManipulableObjectAction.MergeDecks)
            {
                if (e.ManipulableSource != this.winner)
                    e.Cancel = true;
                else
                {
                    this.winner.ShadowVisibility = Visibility.Collapsed;
                    this.winner = null;
                }
            }
            if (e.ManipulableObjectAction == ManipulableObjectAction.MoveCardOrDeckOnBoard)
            {
                if (e.ManipulableSource is Card)
                {
                    Deck tmp = ((Card)e.ManipulableSource).LastParent as Deck;
                    if (tmp != null)
                    {
                        tmp.CardAddingPosition = CardInDeckPosition.Top;
                        this.board.AddCardToDeck(tmp, e.ManipulableSource as Card);
                        tmp.CardAddingPosition = CardInDeckPosition.Bottom;
                        tmp.UpdateDisposition(true, false, .2f);
                    }
                }
            }
            else if (e.ManipulableObjectAction == ManipulableObjectAction.CreateDeck)
            {
                if (this.gameState != GameState.Reset)
                    e.Cancel = true;
            }
            else if (e.ManipulableObjectAction == ManipulableObjectAction.AddCardsToDeck)
            {
                Deck d = e.ManipulableSource as Deck;
                ((Card)e.Cards[0]).ShadowVisibility = Visibility.Collapsed;
                if (d == this.gameDecks[0])
                    e.Cancel = !this.TestMove(e.Cards[0] as Card, d, this.decks[0]);
                else if (d == this.gameDecks[1])
                    e.Cancel = !this.TestMove(e.Cards[0] as Card, d, this.decks[1]);
                else if (d == this.decks[0])
                {
                    if (e.Cards[0].LastParent == this.decks[1])
                    {
                        e.Cancel = true;
                        this.board.AddCardToDeck(e.Cards[0].LastParent as IDeck, e.Cards[0]);
                        this.decks[1].UpdateDisposition(true, true, .5f);
                    }

                }
                else if (d == this.decks[1])
                {
                    if (e.Cards[0].LastParent == this.decks[0])
                    {
                        e.Cancel = true;
                        this.board.AddCardToDeck(e.Cards[0].LastParent as IDeck, e.Cards[0]);
                        this.decks[0].UpdateDisposition(true, true, .5f);
                    }
                }
            }
        }
        /// <summary>
        /// test if a card can be moved in a another deck
        /// </summary>
        /// <param name="added">Card who need to be added</param>
        /// <param name="toDeck">Card actual parent</param>
        /// <param name="fromDeck">Card possible futur parent</param>
        /// <returns></returns>
        private bool TestMove(Card added, Deck toDeck, Deck fromDeck)
        {
            if (added.LastParent != fromDeck || (this.cards.Any() && added.LastParent == this.cards[this.cards.Count - 1].LastParent))
            {
                this.board.AddCardToDeck(added.LastParent as IDeck, added);
                ((Deck)added.LastParent).UpdateDisposition(true);
                return false;
            }
            else
            {
                toDeck.UpdateDisposition(true, true);
                this.cards.Add(added);
                return true;
            }
        }
        /// <summary>
        /// Catch every change in game decks
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Battle_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                if (this.TestGame())
                {
                    this.gameDecks[0].CardAddingFace = CardInDeckFace.FrontUp;
                    this.gameDecks[1].CardAddingFace = CardInDeckFace.FrontUp;
                }
            }
        }
        #endregion

        #region Battle
        /// <summary>
        /// Check if we have a winner or a battle
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FinalizeBattle(Object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add && this.TestGame())
            {
                if (this.battle)
                {
                    this.battle = false;
                    this.gameDecks[0].CardAddingFace = CardInDeckFace.FrontUp;
                    this.gameDecks[1].CardAddingFace = CardInDeckFace.FrontUp;
                }
                else if (this.Evaluate(this.cards[this.cards.Count - 1]) < this.Evaluate(this.cards[this.cards.Count - 2]))
                {
                    ResetGame(this.cards[this.cards.Count - 2].LastParent as Deck);
                }
                else if (this.Evaluate(this.cards[this.cards.Count - 1]) > this.Evaluate(this.cards[this.cards.Count - 2]))
                {
                    ResetGame(this.cards[this.cards.Count - 1].LastParent as Deck);
                }
                else
                {
                    this.gameDecks[0].CardAddingFace = CardInDeckFace.BackUp;
                    this.gameDecks[1].CardAddingFace = CardInDeckFace.BackUp;
                    this.battle = true;
                }
            }
        }
        /// <summary>
        /// Check if the match can be finish or not
        /// </summary>
        /// <returns></returns>
        private bool TestGame()
        {
            return this.cards.Count % 2 == 0 && this.cards.Any();
        }
        #endregion

        #region ResetGame
        /// <summary>
        /// Reset the game after a match
        /// </summary>
        /// <param name="deck">Deck who wins this match</param>
        private void ResetGame(Deck deck)
        {
            this.board.RemoveCardsFromDeck(this.gameDecks[0], this.gameDecks[0].ToList());
            this.board.RemoveCardsFromDeck(this.gameDecks[1], this.gameDecks[1].ToList());

            this.gameState = GameState.Reset;
            Deck tmp = this.CreateDeck();
            this.gameState = GameState.FinalyzeGame;
            tmp.IsMosaicEnabled = false;
            tmp.IsEyeEnabled = false;
            tmp.AutoRemoveWhenEmpty = false;
            tmp.CanScale = false;
            tmp.IsMenuEnabled = false;
            tmp.CanAddByUser = false;
            tmp.CanRemoveByUser = false;
            tmp.CardAddingFace = CardInDeckFace.FrontUp;
            tmp.Center = new Point(512, 389);
            tmp.ScatterViewItem.Loaded += (s, e) => tmp.UpdateDisposition(true, true, 6.0);
            tmp.ScatterViewItem.Unloaded += new RoutedEventHandler(ScatterViewItem_Unloaded);
            this.board.AddCardsToDeck(tmp, this.cards);
            this.cards.Clear();
            this.gameState = GameState.None;
            this.winner = deck;
            deck.ShadowVisibility = Visibility.Visible;
            deck.ShadowColor = Colors.Blue;
            this.gameDecks[0].Visibility = Visibility.Collapsed;
            this.gameDecks[1].Visibility = Visibility.Collapsed;
        }
        /// <summary>
        /// Allow the complet reset of the game
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ScatterViewItem_Unloaded(object sender, RoutedEventArgs e)
        {
            this.gameDecks[0].Visibility = Visibility.Hidden;
            this.gameDecks[1].Visibility = Visibility.Hidden;
        }
        #endregion

        #endregion

        #region IEvaluate Members
        public int Evaluate(ICard card)
        {
            if (card == null)
                return 0;
            BasicCardValue v = (BasicCardValue)card.CardValue;
            return this.GetValue(v.Number);
        }
        public ICard Evaluate(IEnumerable<ICard> cards)
        {
            if (cards == null || !cards.Any())
                return null;

            ICard ret = cards.First();

            foreach (ICard c in cards)
            {
                if (this.GetValue(c) > this.GetValue(ret))
                    ret = c;
            }

            return ret;
        }
        public int Evaluate(IDeck deck)
        {
            if (deck == null)
                return 0;

            return deck.Sum(it => this.GetValue(it));
        }
        public IDeck Evaluate(IEnumerable<IDeck> decks)
        {
            if (decks == null || !decks.Any())
                return null;
            IDeck ret = decks.First();
            foreach (IDeck d in decks)
            {
                if (this.Evaluate(d) > this.Evaluate(ret))
                    ret = d;
            }
            return ret;
        }
        #endregion

        #region Load Value
        private void LoadValue()
        {
            try
            {
                #region Load Value
                //string[] tmp = Assembly.GetExecutingAssembly().GetManifestResourceNames();
                //foreach (string s in tmp)
                //    Console.WriteLine(s);
                this.xml.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("SurfaceCardsGameKit.Games.Battle.Value.xml"));
                #endregion
            }
            catch
            {
                throw new Exception("Error loading Value.xml");
            }
        }
        #endregion

        #region GetValue
        private int GetValue(ICard card)
        {
            BasicCardValue v = (BasicCardValue)card.CardValue;
            return this.GetValue(v.Number);
        }
        private int GetValue(BasicCardsNumber nb)
        {
            XmlElement root = this.xml.DocumentElement;
            XmlNodeList nodes = root.SelectNodes("//Cards");
            foreach (XmlNode node in nodes)
            {
                XmlNodeList tmp = node.ChildNodes;
                foreach (XmlNode n in tmp)
                {
                    XmlAttributeCollection a = n.Attributes;
                    if ((int)nb == Int32.Parse(a[0].Value))
                        return Int32.Parse(n.InnerText);
                }
            }
            return -1;
        }
        #endregion

        protected override void OnDispose()
        {
            this.board.RemoveHandler(Board.ManipulableObjectStateChangingEvent, new ManipulableObjectStateChangingEventHandler(ChangeState));
            this.board.RemoveHandler(ScatterViewItem.ScatterManipulationDeltaEvent, new ScatterManipulationDeltaEventHandler(OnScatterManipulationDelta));
        }
    }
}
